#include "socket.hpp"
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <set>
#include <vector>

#define BUFFER_SIZE 1024

const int MAX_FD = sizeof(fd_set) << 3;

class select_server {
public:
    select_server() {
        
    }

    ~select_server() {
    
    }

    void init() {
        _listen_sock.socket();
        _listen_sock.bind();
        _listen_sock.listen();
        lg(INFO, "server initialized");
    }

    void accepter(vector<int> &need_to_be_added) {
        struct sockaddr_in client;
        int newfd = _listen_sock.accept(&client);
        if(newfd < 0) {
            return;
        }
        // 但只是连接就绪了，并不意味着该连接中的数据就绪了
        // 所以获取连接不会阻塞，但若读取其中的数据是有可能阻塞的
        // 因此该连接fd需要设置进读文件描述符集合
        // 让select去等待数据就绪
        if(_rfds_set.size() == MAX_FD) {
            lg(WARNING, "server is full");
            close(newfd);
            return;
        }
        // 添加
        need_to_be_added.push_back(newfd);

        char ip_buff[32];
        if(inet_ntop(client.sin_family, &client.sin_addr.s_addr, ip_buff, sizeof(ip_buff)) == nullptr) {
            lg(ERROR, "inet_ntop error");
        }
        else {
            lg(INFO, "client_ip = %s, client_port = %d", ip_buff, ntohs(client.sin_port));
        }
    }

    void recever(int fd, vector<int> &need_to_be_deleted) {
        char in_buff[BUFFER_SIZE];
        ssize_t n = read(fd, in_buff, sizeof(in_buff) - 1); // bug，得保证读取完整
        if(n > 0) {
            in_buff[n] = '\0';
            cout << "get a message# " << in_buff << endl;
        }
        else {
            if(n == 0) {
                lg(INFO, "client closed");
            }
            else {
                lg(ERROR, "read error");
            }
            close(fd);
            // 删除
            need_to_be_deleted.push_back(fd);
        }
    }
    
    // 事件派发器
    void dispatcher(fd_set &fds) {
        // 所有被关心的套接字fd存放在_rfds_set中
        // 其中所有就绪的fd放在fds中
        vector<int> need_to_be_deleted, need_to_be_added;
        // 需要遍历判断不同的就绪套接字
        for(int i : _rfds_set) {
            // 判断当前fd是否是就绪的
            if(FD_ISSET(i, &fds)) {
                // 监听套接字就绪
                // 意味着有新连接到，可以accept不会阻塞
                if(i == _listen_sock.get_sockfd()) {
                    // 连接器
                    accepter(need_to_be_added);
                }
                // 其它套接字就绪
                // 这里是读事件就绪，可以直接读取
                else {
                    // 读取器
                    recever(i, need_to_be_deleted);
                }
            }
        }
        // 在遍历容器过程中尽量不要修改容器大小
        // 统一在遍历结束后修改，也就是添加到或者从select中移除
        for(int fd : need_to_be_deleted) {
            _rfds_set.erase(fd);
        }
        for(int fd : need_to_be_added) {
            _rfds_set.insert(fd);
        }
    }

    void start() {
        // 用于监听的套接字文件是用来等待新连接的到来，等待上层读取
        // 所以一开始就需要设置进读文件描述符集合
        _rfds_set.insert(_listen_sock.get_sockfd());
        while(true) {
            // 注意此处不能直接accept，因为若无新连接到来会阻塞等待
            // 也就是说accept的本质就是在阻塞检测新连接事件是否就绪，也就是否有三次握手
            // 而accept一次只能阻塞等待一个fd，那说好的等待多个fd呢，所以不能直接accept，要交给select等待
            // 当新连接到来，会被系统添加到内核的全连接队列中等待上层取走，也就意味着读事件就绪
            // 在多路转接模型中，等待这个工作是专门由select来做的
            // 当有事件就绪select会通过设置fd_set中的标志位来告知上层，也就是我

            // 后面四个作为输入输出型参数在一次调用后可能会被修改
            // 需要重新修改或设置
            // 比如说位图为1111，调用结束后若没有事件就绪那么该结构内容会被覆盖为0000
            // 但下次调用依旧还需要关心1111这几个fd，所以需要重新设置
            fd_set rfds;
            FD_ZERO(&rfds);
            cout << "online fds: ";
            for(int fd : _rfds_set) {
                FD_SET(fd, &rfds);
                cout << fd << " ";
            }
            cout << endl;
            // 也需要重新设置
            struct timeval t = {0, 0}; //{0, 0} 非阻塞
            int mx_fd = *--_rfds_set.end();
            // 上面只是在用户层设置fd_set还没有设置进内核，通过select设置到内核中
            // 最后一个参数设置为nullptr表示阻塞等待，直到事件就绪
            int n = select(mx_fd + 1, &rfds, nullptr, nullptr, nullptr);
            // 注意，若某事件就绪且上层并对其进行处理，那么select会一直通知你，直到对其处理
            // 就绪后的一次读取不会被阻塞，直接读取
            if(n > 0) {
                cout << "something is ready" << endl;
                dispatcher(rfds);
            }
            else {
                if(n == 0) {
                    lg(INFO, "select timeout");
                    sleep(1);
                }
                else {
                    lg(ERROR, "select error");
                    break;
                }
            }
        }
    }
private:
    Socket _listen_sock;
    // 为了方便在不同的函数中添加要关心的fd
    // 一般需要用到一个辅助数组来保存这些fd
    // 然后再依次添加到对应的fd_set中
    // 使用set为了一次找到最大的fd
    set<int> _rfds_set;
};